package top.broncho.lottery.data.paging

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.paging.DataSource
import androidx.paging.LivePagedListBuilder
import androidx.paging.PageKeyedDataSource
import androidx.paging.PagedList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import org.jetbrains.anko.AnkoLogger
import org.jetbrains.anko.info
import top.broncho.lottery.api.LotteryApi
import top.broncho.lottery.model.history.LotteryRes

private const val PAGE_SIZE = 50

fun LotteryApi.getListing(): Listing<LotteryRes> {
    val api = this
    val sourceFactory = object : DataSource.Factory<Int, LotteryRes>() {
        val sourceLiveData = MutableLiveData<LotteryDataSource>()
        override fun create(): DataSource<Int, LotteryRes> {
            //初始化创建数据源对象，加载上一页或下一页不会创建
            return LotteryDataSource(api).apply { sourceLiveData.postValue(this) }
        }
    }

    val config = PagedList.Config.Builder()
        .setPageSize(PAGE_SIZE)
        .setInitialLoadSizeHint(PAGE_SIZE)
        .setEnablePlaceholders(false)
        .build()
    return Listing(
        pagedList = LivePagedListBuilder<Int, LotteryRes>(sourceFactory, config).build(),
        loadingState = Transformations.switchMap(sourceFactory.sourceLiveData) { it.loadingState },
        refresh = { sourceFactory.sourceLiveData.value?.invalidate() },
        retry = { sourceFactory.sourceLiveData.value?.retry() }
    )
}

private class LotteryDataSource(private val api: LotteryApi) :
    PageKeyedDataSource<Int, LotteryRes>(),
    AnkoLogger, CoroutineScope by MainScope() {

    private var retry: (() -> Any)? = null
    val loadingState = MutableLiveData<LoadingState>()

    fun retry() {
        retry?.let {
            //重试请求
            retry = null
            it()
        }
    }

    override fun loadInitial(
        params: LoadInitialParams<Int>,
        callback: LoadInitialCallback<Int, LotteryRes>
    ) {
        launch {
            info { "loadInitial...E" }
            loadingState.postValue(Loading(true))
            try {
                val response = api.history(1, params.requestedLoadSize)
                if (response.error_code == 0) {
                    val list = response.result?.lotteryResList
                    info { "loadInitial: onResponse isSuccess = ${list?.isNotEmpty()}" }
                    loadingState.postValue(
                        if (list.isNullOrEmpty()) Empty(true) else Succeed(true)
                    )
                    callback.onResult(list ?: emptyList(), null, 2)
                } else {
                    retry = {
                        loadInitial(params, callback)
                    }
                    loadingState.postValue(Failed(Throwable(response.reason), true))
                }
            } catch (e: Exception) {
                info { "loadInitial: onFailure throwable = $e" }
                retry = {
                    loadInitial(params, callback)
                }
                loadingState.postValue(Failed(e, true))
            }
            info { "loadInitial...X" }
        }
    }

    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, LotteryRes>) {
        launch {
            info { "loadAfter...E" }
            loadingState.postValue(Loading())
            try {
                val response = api.history(params.key, params.requestedLoadSize)
                if (response.error_code == 0) {
                    val list = response.result?.lotteryResList
                    info { "loadAfter: onResponse isSuccess = ${list?.isNotEmpty()}" }
                    loadingState.postValue(if (list.isNullOrEmpty() || list.size < PAGE_SIZE) NotMore() else Succeed())
                    callback.onResult(list ?: emptyList(), params.key + 1)
                } else {
                    retry = {
                        loadAfter(params, callback)
                    }
                    loadingState.postValue(Failed(Throwable(response.reason)))
                }
            } catch (e: Exception) {
                info { "loadAfter: onFailure throwable = $e" }
                retry = {
                    loadAfter(params, callback)
                }
                loadingState.postValue(Failed(e))
            }
            info { "loadAfter...X" }
        }
    }

    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, LotteryRes>) {
    }

}